# -*- coding: utf-8 -*-

from django.db import models
from django.contrib.auth.models import User
from django.conf import settings
import datetime
from os import popen

def T0():
    return datetime.datetime.fromtimestamp(0)

class BigIntegerField(models.IntegerField):
    empty_strings_allowed=False
    def get_internal_type(self):
        return "BigIntegerField"

    def db_type(self):
        return 'NUMBER(19)' if settings.DATABASE_ENGINE == 'oracle' else 'bigint'


class TrafficCategory(models.Model):
    name = models.CharField(u"Название", max_length=80, unique=True)
    is_public = models.BooleanField(u"Общедоступный?", default=True, \
                help_text="Показывать ли его пользователям?")

    def __unicode__(self):
        return self.name

    class Meta:
        verbose_name = u"Категория трафика"
        verbose_name_plural = u"Категории трафика"


class Tariff(models.Model):
    name = models.CharField(u"Название", max_length=80, unique=True)
    cost = models.FloatField(u"Стоимость", default=0, help_text=\
                "размер абонплаты")
    period = models.SmallIntegerField(u"Период", default=30, \
                help_text="Периодичность абонплаты (дней)") # in days
    is_public = models.BooleanField(u"Общедоступный?", default=True, \
                help_text="Показывать ли этот тариф пользователям?")
    rules = models.ManyToManyField(TrafficCategory, through='TariffRule')

    def __unicode__(self):
        return self.name

    class Meta:
        verbose_name = u"Тариф"
        verbose_name_plural = u"Тарифы"


class TariffRule(models.Model):
    traffic_category = models.ForeignKey(TrafficCategory, \
            verbose_name=u"Категория трафика")
    tariff = models.ForeignKey(Tariff, verbose_name=u"Тариф")
    packet = BigIntegerField(u"Пакет", default=0, help_text=\
            "Объём предоплаченного трафика, байт")
    excess_price = models.FloatField(u"Превышение", help_text=\
            "Стоимость превышения, грн. за мегабайт")

    def __unicode__(self):
        return u"Правило для трафика '%s'" % unicode(self.traffic_category)

    class Meta:
        unique_together = (("traffic_category", "tariff"),)
        verbose_name = u"Тарифное правило"
        verbose_name_plural = u"Тарифные правила"


class InternetChannel(models.Model):
    name = models.CharField(u"Название", max_length=80, unique=True)
    table = models.CharField(u"Имя таблицы (iproute)", max_length=80)

    def __unicode__(self):
        return self.name

    class Meta:
        verbose_name = u"Канал"
        verbose_name_plural = u"Каналы"


class UserProfile(models.Model):
    user = models.ForeignKey(User, unique=True, verbose_name=u"пользователь")
    ip = models.IPAddressField("IP")
    tariff = models.ForeignKey(Tariff, verbose_name=u"Тариф")
    balance = models.FloatField(u"Баланс", default=0)
    credit = models.FloatField(u"Кредит", default=0, help_text=\
        u"на сколько можно уходить в минус?")
    blocked = models.BooleanField(u"Заблокирован", default=False)
    inet = models.BooleanField(u"Интернет", default=False, \
        help_text=u"Включён ли?")
    inet_until = models.DateTimeField(u"Насколько включён интернет?", \
        default=T0)
    paid_until = models.DateTimeField(u"Насколько оплачен интернет?", \
        default=T0)
    last_activity = models.DateTimeField(u"Последнее подключение", \
        default=T0)
    address = models.TextField(u"Адрес", blank=True, default='')
    key = models.CharField(u"Ключик для авторизатора", max_length=100, \
        default='', help_text=u"Руками трогать не надо", blank=True)
    comment = models.TextField(u"Комментарий", blank=True, default='')
    prepaid_packets = models.ManyToManyField(TrafficCategory, \
        through='PrepaidPacket')
    channel = models.ForeignKey(InternetChannel, \
        verbose_name=u"Канал подключения к интернету")

    def __unicode__(self):
        return u"%s %s" % (self.user.last_name, \
            self.user.first_name)

    def pay(self, amount, cashier=None, category=None, comment=''):
        """incoming payment, amount may be < 0"""
        p = Payment(user=self, cashier=cashier, category=category, \
            comment=comment, sum=amount)
        p.save()
        self.balance += amount
        self.save()

    def change_tariff(self, tariff, clear):
        if clear:
            # удаляем весь предоплаченный трафик
            self.prepaidpacket_set.all().delete()
        self.tariff = tariff
        self.paid_until = T0()
        self.save()

    def change_channel(self, channel):
        self.channel = channel
        self.update_firewall()
        self.save()

    def abon_paid(self):
        return self.paid_until > datetime.datetime.now()
        
    def check(self):
        """
        Will see at itself, and do smth
        """
        # удаляем потраченные пакеты
        self.prepaidpacket_set.filter(prepaid=0).delete()
        # если нужно и баланса хватает, то продлеваем тариф
        if not self.abon_paid():
            # пора продлить тариф
            if (self.balance+self.credit>=self.tariff.cost) or (self.tariff.cost==0):
                # если достаточно денег для абонплаты или тариф без неё
                try: category = PaymentCategory.objects.get(name=u"абонплата")
                except PaymentCategory.DoesNotExist: category = None
                if self.tariff.cost:
                    self.pay(-self.tariff.cost, category=category, \
                        comment=u"Абонплата за тариф %s" % self.tariff)
                self.paid_until = datetime.datetime.now() + \
                                  datetime.timedelta(days=self.tariff.period)
                for rule in self.tariff.tariffrule_set.all():
                    try:
                        packet = self.prepaidpacket_set.get(traffic_category=\
                                    rule.traffic_category)
                    except PrepaidPacket.DoesNotExist:
                        packet = self.prepaidpacket_set.create(
                            traffic_category=rule.traffic_category)
                    packet.prepaid += rule.packet
                    packet.save()
        # блокируем аккаунт если надо
        self.blocked = self.need_blocking()
        # если заблокирован или пора отключать по таймеру
        if self.blocked or self.inet_until<datetime.datetime.now():
            self.turn_inet(False)
        self.save()

    def need_blocking(self):
        # проверяем баланс, и абонплату
        return (self.balance+self.credit<0) or (self.tariff.name=="Blocked") \
            or not self.abon_paid()

    def turn_inet(self, status, delta=datetime.timedelta(30)):
        if self.blocked: status = False
        if status:
            self.inet_until = datetime.datetime.now() + delta
        if self.inet != status:
            self.inet = status
            self.update_firewall()
        self.save()

    def turn_reserve(self, status):
        if self.use_reserve != status:
            self.use_reserve = status
            self.update_firewall()
        self.save()

    def update_firewall(self):
        if self.inet:
            ts = " ".join([str(i.traffic_category.id) for i in self.tariff.tariffrule_set.all()])
            popen("%s up %s %s" % (settings.FIREWALL_SCRIPT, self.ip, ts)).read()
        else:
            popen("%s down %s" % (settings.FIREWALL_SCRIPT, self.ip, )).read()

        popen("%s channel %s %s" % (settings.FIREWALL_SCRIPT, self.ip, self.channel.table)).read()

    def update_key(self, password):
        from md5 import md5
        from sha import sha
        self.key = sha(self.user.username.encode("utf8")+md5(password.encode("utf8")).hexdigest()).hexdigest()
        self.save()

    def check_key(self, key):
        """
        проверяет, валидный ли дали ключ
        """
        return key==self.key

    def handle_traffic(self, date, hour, category, traffic, unitonly=False):
        incoming, outgoing = traffic
        total = incoming + outgoing
        try:
            unit = self.traffic.get(date=date, hour=hour, traffic_category=category)
        except TrafficUnit.DoesNotExist:
            unit = self.traffic.create(date=date, hour=hour, traffic_category=category)
        unit.incoming += incoming
        unit.outgoing += outgoing
        if unitonly:
            unit.save()
            return unit
        try:
            # если есть пакет, то пользуемся им, пока возможно
            packet = self.prepaidpacket_set.get(traffic_category=category)
            q = min(packet.prepaid, total)
            packet.prepaid -= q
            total -= q
            packet.save()
        except PrepaidPacket.DoesNotExist:
            pass
        self.last_activity = datetime.datetime.now()
        cost = self.tariff.tariffrule_set.get(traffic_category=category).excess_price
        cost = cost * total / 1024 / 1024 # т.к. цена указана за мегабайт, а у нас байты
        unit.cost += cost
        unit.save()
        self.balance -= cost
        self.save()
        return unit

    class Meta:
        verbose_name = u"Профиль"
        verbose_name_plural = u"Профили"


class PrepaidPacket(models.Model):
    user = models.ForeignKey(UserProfile, verbose_name=u"Пользователь")
    traffic_category = models.ForeignKey(TrafficCategory, \
        verbose_name=u"Тип трафика")
    prepaid = BigIntegerField(u"Предоплачено", \
        default=0, help_text="Объём предоплаченного трафика, байт")

    class Meta:
        unique_together = (("traffic_category", "user"),)


class PaymentCategory(models.Model):
    name = models.CharField(u"Название", max_length=80, unique=True)

    class Meta:
        verbose_name = u"Категория платежей"
        verbose_name_plural = u"Категории платежей"

    def __unicode__(self):
        return self.name


class Payment(models.Model):
    user = models.ForeignKey(UserProfile, verbose_name=u"Пользователь",
        related_name='payments', editable=False)
    cashier = models.ForeignKey(UserProfile, verbose_name=u"Кассир", 
        blank=True, null=True, related_name='received_payments', editable=False)
    date = models.DateTimeField(u"Дата платежа", default=datetime.datetime.now,
            editable=False)
    category = models.ForeignKey(PaymentCategory, verbose_name=u"Тип платежа", 
        null=True)
    sum = models.FloatField(u"Сумма, грн.")
    comment = models.TextField(u"Комментарий", blank=True, default='')

    def __unicode__(self):
        return u"Платёж %s %s на %.2f грн, от %s" % \
            (self.user.user.first_name, self.user.user.last_name, \
            self.sum, self.date)

    class Meta:
        verbose_name = u"Платёж"
        verbose_name_plural = u"Платежи"

class TrafficUnit(models.Model):
    user = models.ForeignKey(UserProfile, verbose_name=u"Пользователь",
        related_name='traffic', editable=False, null=True)
    date = models.DateField(u"Дата", editable=False)
    hour = models.IntegerField(u"Час", editable=False)
    traffic_category = models.ForeignKey(TrafficCategory, \
            verbose_name=u"Категория трафика")
    incoming = BigIntegerField(default=0)
    outgoing = BigIntegerField(default=0)
    cost = models.FloatField(default=0)

    def __unicode__(self):
        return u"%s, %s ч.: %s, %s, [IN: %d, OUT: %d]" % (self.date, \
            self.hour, self.user, self.traffic_category, self.incoming, \
            self.outgoing)

    class Meta:
        unique_together = (("user", "date", "hour", "traffic_category"),)

class BadLine(models.Model):
    line = models.TextField()
    label = models.CharField(max_length=10)
    date = models.DateTimeField(default=datetime.datetime.now)

    def __unicode__(self):
        return u"<%s>: %s" % (self.label, self.line)

#from django.contrib import admin
#try: admin.site.register(InternetChannel)
#except: pass

